﻿#include <stdio.h>
//指针
//1.解引用操作符
//2.指针 + -运算
//3.强制类型转换
//4.void指针
//5.指针法列数组
//6.元素差和字节长度
//                                      ！！！！！！！！！ 重头戏，指针！！！！！！！！！！！！！！！！
//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	printf("%d\n", *pa);//* - 解引用操作符(间接访问操作符)  //*pa就是a
//	*pa = 20;
//	printf("%d\n", a);
//
//	return 0;
//}


//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	
//	char ch = 'w';
//	char* pc = &ch;
//
//	printf("%zd\n", sizeof(pa));//
//	printf("%zd\n", sizeof(pc));//?
//
//	return 0;
//}
// 
// 
// 
// 
//                 解引用操作符 ！！！！！
//int main()
//{
//	int a = 0x11223344;
//	int* pa = &a;
//	*pa = 0;//赋值操作！！！
//
//	return 0;
//}
//
//int main()
//{
//	int a = 0x11223344;
//	char* pa = &a;//pa能不能存的下a的地址?
//	*pa = 0;
//
//	return 0;
//}




//                  结论！！！：指针的类型决定了指针向前或者向后⾛⼀步有多⼤（距离）。
//#include <stdio.h>
//int main()
//{
//	int n = 10;
//	char* pc = (char*)&n;//强制类型转换！！！后面（char *)&n
//	int* pi = &n;
//	printf("%p\n", &n);//00AFF974
//	printf("%p\n", pc);//00AFF974
//	printf("%p\n", pc + 1);//00AFF975
//	printf("%p\n", pi);//00AFF974
//	printf("%p\n", pi + 1);//00AFF978
//	return 0;
//	我们可以看出， char* 类型的指针变量 + 1跳过1个字节， int* 类型的指针变量 + 1跳过了4个字节。
//	这就是指针变量的类型差异带来的变化。指针 + 1，其实跳过1个指针指向的元素。指针可以 + 1，那也可		以 - 1。
//
////         
//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	char* pc =(char*) & a;
//
//	printf("&a = %p\n", &a);
//
//	printf("pa   = %p\n", pa);
//	printf("pa+1 = %p\n", pa + 1);
//
//	printf("pc   = %p\n", pc);
//	printf("pc+1 = %p\n", pc + 1);
//	///*&a =     000000E99E6FF984
//	//	pa =     000000E99E6FF984
//	//	pa + 1 = 000000E99E6FF988
//	//	pc =     000000E99E6FF984
//	//	pc + 1 = 000000E99E6FF985*/
//
//	return 0;
//}




//            强制类型转换！！！
// 
//将 pc 改为指向 char 类型的指针，并进行类型转换：
//如果我们确实需要 pc 是一个指向 char 类型的指针，并且希望它指向 int 类型变量 a 的第一个字节，可以进行显式的类型转换：
//int a = 10;
//int* pa = &a;
//char* pc = (char*)&a;
//这种情况下，pc 将指向 int 变量 a 的第一个字节。
// !!!
// 需要注意的是，通过 char* 指针访问 int 变量时，只能访问和修改该变量的一个字节。





                             //void* 指针
//在指针类型中有⼀种特殊的类型是 void* 类型的，可以理解为⽆具体类型的指针（或者叫泛型指
//针），这种类型的指针可以⽤来接受任意类型地址。但是也有局限性， void* 类型的指针不能直接进
//⾏指针的 + -整数和解引⽤的运算。
//举例：

//int main()
//{
//	int a = 10;
//	int* pa = &a;
//	char* pc = &a;//"int *"类型的值不能用于初始化"char *"类型的实体
//	return 0;
//}

//在上⾯的代码中，将⼀个int类型的变量的地址赋值给⼀个char * 类型的指针变量。编译器给出了⼀个警
//告（如下图），是因为类型不兼容。⽽使⽤void * 类型就不会有这样的问题。

//int main()
//{
//	int a = 10;         //0x000000000000000A
//	int* pa    = &a;
//	//int *  ---int *
//	char* pc    = &a;
//	//char * ---int *
//	void* pv = &a;
//	//void*  ---int*
//	//常见错误：*pv = 20;
//	        //  pv++
//	  
//	//void *p = &a;
//	//这意味着你把变量 a 的地址赋给了一个通用指针 p。
//	//但由于 void* 不包含类型信息，使用时必须进行显式类型转换。
//	
//	                                   举例：
//#include <stdio.h>
//
//int main()
//{
//		int a = 10;
//		void* p = &a; // 合法：void*可以存储任何类型的地址
//
//		
//		 printf("%d\n", *p); 
//		 错误：不能直接解引用void*
//	
//		int* pa = (int*)p;
//		printf("%d\n", *pa); // 输出:10
//		正确：必须先转换为正确的指针类型
//	
//	return 0;
//}



// !!!!!!!!!!!!!          容易混淆的概念！！！！！！！！！！！！！！


//int a = 10; // 声明一个整型变量 a，并赋值为 10
//int* pa = &a; // 声明一个指向整型的指针变量 pa，并将其初始化为 a 的地址
//// 解引用操作符 * 的使用
//printf("%d\n", *pa); // 输出 pa 所指向地址中的数据，即 a 的值，输出结果为 10
//// 修改指针所指向的数据
//*pa = 20; // 将 pa 所指向地址中的数据修改为 20
//printf("%d\n", a); // 输出 a 的值，输出结果为 20

//* 号
//在C语言中具有双重含义：在声明中表示指针类型，在操作中作为解引用操作符。
//解引用操作是指通过指针变量存储的地址，访问该地址所指向的数据。
//
//pa 
//是变量名，它存储的是一个地址，这个地址指向一个 int 类型的数据。在 int* pa = &a; 中，pa 存储的是变量 a 的地址。
//pa 变量本身存储的是一个内存地址，通过这个地址可以访问内存中的实际数据。
//
//int*
//是一个类型声明，表示 pa 是一个指向 int 类型数据的指针变量。它表示 pa 可以存储一个地址，这个地址指向的数据类型是 int。
//在声明中，* 表示 pa 是一个指针变量，它可以存储一个内存地址。
//
//解引用操作符详解
//
//解引用操作符的作用
//解引用操作符* 用于访问指针变量所指向的内存地址中的数据。例如，* pa 表示访问 pa 所指向的地址中的数据。
//
//
//修改指针所指向的数据
//可以通过解引用操作符修改指针所指向的数据，例如* pa = 20; 将 a 的值修改为 20。
//
//
//访问指针所指向的数据
//当 * 用于已经声明的指针变量（如 * pa）时，它表示解引用操作，可以访问该地址所存储的数据值。


//                       运用指针的的运算关系打印出数组中的各个数字
//     
//                          共计三种方法
// 第一种：

//int main()
//
//{
//	int arr[] = { 1,2,3,4,5,6,7,8,9 };
//	int i = 0;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[0];
//	printf("%p\n", &arr[0]);//0000000BC6CFFA18  /  00000023690FF688
//		
//	printf("循环开始\n");
//	for (i = 0;i < sz;i++)
//	{
//		printf("%d\n",*(p +i ));
//
//	}
//	return 0;
//}
//第一个问题：
//
//           printf("%d\n", p + i); 和 printf("%d\n", *(p + i)); 的区别和联系
//
//区别：
//printf("%d\n", p + i);：这里 p + i 是指针运算，计算的是数组中第 i 个元素的地址。因此，p + i 的结果是一个地址，而不是数组元素的值。使用 % d 格式说明符来打印地址会导致未定义行为，因为 % d 期望的是一个整数而不是地址。
//printf("%d\n", *(p + i));：这里* (p + i) 是解引用操作，获取的是数组中第 i 个元素的值。因此，* (p + i) 的结果是一个整数，可以正确地使用% d 格式说明符来打印。
//
//联系：
//两者都涉及指针运算 p + i，其中 p 是指向数组第一个元素的指针，i 是索引。
//p + i 计算的是数组中第 i 个元素的地址。
//* (p + i) 是对 p + i 的解引用，获取的是数组中第 i 个元素的值。
//
//
//第二个问题：
//
//          为什么运行两次两次出来的 arr[0] 的地址不一样，一次是 0000000BC6CFFA18 而另一次是 00000023690FF688
//原因：
//
//在不同的程序运行中，操作系统可能会为程序分配不同的内存地址。这种现象被称为 地址空间布局随机化（Address Space Layout Randomization, ASLR）。
//ASLR 是一种安全机制，旨在防止攻击者通过已知的内存地址来执行代码注入等攻击。
//每次程序运行时，操作系统可能会为程序分配不同的内存区域，因此 arr[0] 的地址也会有所不同。
//具体解释：
//
//在第一次运行时，操作系统可能为 arr 分配了地址 0000000BC6CFFA18。
//在第二次运行时，操作系统可能为 arr 分配了不同的地址 00000023690FF688。
//这种变化是正常的，并且是为了提高系统的安全性。
//总结
//在使用指针时，要注意区分指针运算和解引用操作。p + i 是地址运算，而 * (p + i) 是解引用操作，获取的是数组元素的值。
//程序每次运行时，由于 ASLR 的机制，数组的起始地址可能会有所不同，这是正常的安全措施。

// !!!!!!!  反过来，换种思路，同样可行！！！

//  第二种：
//倒序
//int main()
//
//{
//	int i = 0;
//	int arr[] = { 1,2,3,4,5,6,7,8,9 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* address = &arr[sz-1];//容易犯错数组市是从零开始记数，所以要数组长度减一来访问最后的值
//	for (i = 0;i < sz;i++)
//	{
//		printf("%d\n", *(address - i));
//	}
//	return 0;
//}
//输出结果：
//- 858993460        溢出
//9
//8
//7
//6
//5
//4
//3
//2

// 第三种：

//int main()
//{
//    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    int* p = &arr[0];
//
//    for (int i = 0;i < sz;i++)//     明白这个保证的是循环的次数！！！
//    {
//
//        printf("%d\n",*p);
//        p++;
//    }
//    return 0;
//}


int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", (char*)&arr[9] - (char*)&arr[0]); 

	return 0;
}
//你的代码输出结果是 9，
//但这不是表示 9个字节的长度，而是表示 9个元素的偏移量（即 arr[9] 和 arr[0] 之间相差 9个元素）。
//
//详细解释
//1. &arr[9] - &arr[0] 的含义
//&arr[0]：指向数组第一个元素的地址（即 arr[0]）。
//
//&arr[9]：指向数组第10个元素的地址（即 arr[9]）。
//
//指针相减 (&arr[9] - &arr[0])
//
//计算的是两个指针之间的元素个数（而不是字节数）。
//指针相减的结果会自动除以元素大小，所以最终得到的是 元素个数。
// 
//如果你想计算字节长度（即 arr[9] 和 arr[0] 之间的字节数）
//可以这样：
//
//printf("%d\n", (char *)&arr[9] - (char *)&arr[0]); // 36（假设 int=4字节

//      &arr[9] - &arr[0];
//计算的是元素个数差（结果是9）
//因为编译器会自动除以sizeof(int)
//相当于 (地址差) / sizeof(int)
//所以结果是元素个数
//而不是直接的内存地址差	
